﻿using Microsoft.AspNetCore.Mvc;
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;
using System.Threading.Tasks;

namespace ZhCun.WebUtils
{
    public static class JsonElementEx
    {
        public static string GetString(this JsonElement je, string propertyName)
        {
            if (je.TryGetProperty(propertyName, out JsonElement pe))
            {
                return pe.GetString();
            }
            return null;
        }

        public static int GetInt32(this JsonElement je, string propertyName)
        {
            if (je.TryGetProperty(propertyName, out JsonElement pe))
            {
                return pe.GetInt32();
            }
            return 0;
        }

        public static bool GetBoolean(this JsonElement je, string propertyName)
        {
            if (je.TryGetProperty(propertyName, out JsonElement pe))
            {
                return pe.GetBoolean();
            }
            return false;
        }
    }

    /// <summary>
    /// json 帮助类
    /// </summary>
    public static class JsonHelper
    {
        static JsonHelper()
        {
            Options = new JsonOptions();
            SetJsonOption(Options);
        }

        public static void SetJsonOption(JsonOptions options)
        {
            if (options == null) options = new JsonOptions();
            options.JsonSerializerOptions.PropertyNamingPolicy = null; //首字母小写问题
            options.JsonSerializerOptions.Converters.Add(DateTimeConverter.Instance);
            options.JsonSerializerOptions.Converters.Add(DateTimeNullableConverter.Instance);
            options.JsonSerializerOptions.Converters.Add(IntToStringConverter.Instance);
            options.JsonSerializerOptions.Converters.Add(BoolConverter.Instance);
            options.JsonSerializerOptions.Converters.Add(TimeSpanConvert.Instance);
            options.JsonSerializerOptions.Converters.Add(TimeSpanNullAbleConvert.Instance);
            options.JsonSerializerOptions.Converters.Add(DynamicJsonConverter.Instance);

            
            //https://q.cnblogs.com/q/115234/ 关于中文编码问题
            //options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
            options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
            options.JsonSerializerOptions.IgnoreNullValues = true;

            //https://www.cnblogs.com/taadis/p/12165101.html 可参数转换失败的问题

        }

        public static JsonOptions Options { get; private set; }

        public static T Deserialize<T>(string json)
        {
            return JsonSerializer.Deserialize<T>(json, Options.JsonSerializerOptions);
        }

        public static string Serialize<T>(T tVal)
        {
            return JsonSerializer.Serialize(tVal, Options.JsonSerializerOptions);
        }
    }

    class TimeSpanConvert : JsonConverter<TimeSpan>
    {
        static TimeSpanConvert()
        {
            Instance = new TimeSpanConvert();
        }

        public static TimeSpanConvert Instance { get; }

        public override TimeSpan Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
        {
            return TimeSpan.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString());
        }

    }

    class TimeSpanNullAbleConvert : JsonConverter<TimeSpan?>
    {
        static TimeSpanNullAbleConvert()
        {
            Instance = new TimeSpanNullAbleConvert();
        }

        public static TimeSpanNullAbleConvert Instance { get; }


        public override TimeSpan? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return string.IsNullOrEmpty(reader.GetString()) ? default(TimeSpan?) : TimeSpan.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, TimeSpan? value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value?.ToString());
        }
    }

    class DateTimeConverter : JsonConverter<DateTime>
    {
        static DateTimeConverter()
        {
            Instance = new DateTimeConverter();
        }

        public static DateTimeConverter Instance { get; }

        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return DateTime.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
        }
    }

    class DateTimeNullableConverter : JsonConverter<DateTime?>
    {
        static DateTimeNullableConverter()
        {
            Instance = new DateTimeNullableConverter();
        }

        public static DateTimeNullableConverter Instance { get; }


        public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return string.IsNullOrEmpty(reader.GetString()) ? default(DateTime?) : DateTime.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value?.ToString("yyyy-MM-dd HH:mm:ss"));
        }
    }

    class IntToStringConverter : JsonConverter<int>
    {
        static IntToStringConverter()
        {
            Instance = new IntToStringConverter();
        }

        public static IntToStringConverter Instance { get; }

        public override int Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
                if (Utf8Parser.TryParse(span, out int number, out int bytesConsumed) && span.Length == bytesConsumed)
                {
                    return number;
                }

                if (int.TryParse(reader.GetString(), out number))
                {
                    return number;
                }
            }
            return reader.GetInt32();
        }

        public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
        {
            //writer.WriteStringValue(value.ToString());
            writer.WriteNumberValue(value);
        }
    }

    class BoolConverter : JsonConverter<bool>
    {
        static BoolConverter()
        {
            Instance = new BoolConverter();
        }

        public static BoolConverter Instance { get; }

        public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
                if (Utf8Parser.TryParse(span, out bool number, out int bytesConsumed) && span.Length == bytesConsumed)
                {
                    return number;
                }

                if (bool.TryParse(reader.GetString(), out number))
                {
                    return number;
                }
            }
            return reader.GetBoolean();
        }

        public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
        {
            writer.WriteBooleanValue(value);

            //writer.WriteStringValue(value);
        }
    }

    // <summary>
    /// Temp Dynamic Converter
    /// by:tchivs@live.cn
    /// </summary>
    public class DynamicJsonConverter : JsonConverter<dynamic>
    {
        static DynamicJsonConverter()
        {
            Instance = new DynamicJsonConverter();
        }

        public static DynamicJsonConverter Instance { get; }

        public override dynamic Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {

            if (reader.TokenType == JsonTokenType.True)
            {
                return true;
            }

            if (reader.TokenType == JsonTokenType.False)
            {
                return false;
            }

            if (reader.TokenType == JsonTokenType.Number)
            {
                if (reader.TryGetInt64(out long l))
                {
                    return l;
                }

                return reader.GetDouble();
            }

            if (reader.TokenType == JsonTokenType.String)
            {
                if (reader.TryGetDateTime(out DateTime datetime))
                {
                    return datetime;
                }

                return reader.GetString();
            }

            if (reader.TokenType == JsonTokenType.StartObject)
            {
                using JsonDocument documentV = JsonDocument.ParseValue(ref reader);
                return ReadObject(documentV.RootElement);
            }
            // Use JsonElement as fallback.
            // Newtonsoft uses JArray or JObject.
            JsonDocument document = JsonDocument.ParseValue(ref reader);
            return document.RootElement.Clone();
        }

        private object ReadObject(JsonElement jsonElement)
        {
            IDictionary<string, object> expandoObject = new ExpandoObject();
            foreach (var obj in jsonElement.EnumerateObject())
            {
                var k = obj.Name;
                var value = ReadValue(obj.Value);
                expandoObject[k] = value;
            }
            return expandoObject;
        }

        private object ReadValue(JsonElement jsonElement)
        {
            object result;
            switch (jsonElement.ValueKind)
            {
                case JsonValueKind.Object:
                    result = ReadObject(jsonElement);
                    break;
                case JsonValueKind.Array:
                    result = ReadList(jsonElement);
                    break;
                case JsonValueKind.String:
                    //TODO: Missing Datetime&Bytes Convert
                    result = jsonElement.GetString();
                    break;
                case JsonValueKind.Number:
                    //TODO: more num type
                    result = 0;
                    if (jsonElement.TryGetInt32(out int l))
                    {
                        result = l;
                    }
                    break;
                case JsonValueKind.True:
                    result = true;
                    break;
                case JsonValueKind.False:
                    result = false;
                    break;
                case JsonValueKind.Undefined:
                case JsonValueKind.Null:
                    result = null;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return result;
        }

        private object? ReadList(JsonElement jsonElement)
        {
            IList<object?> list = new List<object?>();
            foreach (var item in jsonElement.EnumerateArray())
            {
                list.Add(ReadValue(item));
            }
            return list.Count == 0 ? null : list;
        }

        public override void Write(Utf8JsonWriter writer,
            object value,
            JsonSerializerOptions options)
        {
            // writer.WriteStringValue(value.ToString());
        }
    }
}